﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace XwingGame
{
    public class AsteroidField
    {
        private XwingGame game;
        private Model[] models = new Model[4];
        private Random rand = new Random();

        private LinkedList<Asteroid> asteroids = new LinkedList<Asteroid>();


        public class Asteroid
        {
            public int model;
            public Vector3 position;
            public Vector3 velocity;
            public float size;
            public Vector3 rotationDirection;
            public float rotationSpeed;
            public float rotationAngle;
        }

        public LinkedList<Asteroid> Asteroids { get { return asteroids; } }

        public AsteroidField(XwingGame game)
        {
            this.game = game;
        }

        /// <summary>
        /// This function is called to load content into this component
        /// of our game.
        /// </summary>
        /// <param name="content">The content manager to load from.</param>
        public void LoadContent(ContentManager content)
        {
            models[0] = content.Load<Model>("Asteroid1");
            models[1] = content.Load<Model>("Asteroid2");
            models[2] = content.Load<Model>("Asteroid3");
            models[3] = content.Load<Model>("Asteroid4");
        }

        /// <summary>
        /// This function is called to update this component of our game
        /// to the current game time.
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime)
        {
            float delta = (float)gameTime.ElapsedGameTime.TotalSeconds;
            foreach (Asteroid asteroid in asteroids)
            {
                asteroid.position += asteroid.velocity * delta;
                asteroid.rotationAngle += asteroid.rotationSpeed * delta;
            }
        }


        /// <summary>
        /// This function is called to draw this game component.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="gameTime"></param>
        public void Draw(GraphicsDeviceManager graphics, GameTime gameTime)
        {
            foreach(Asteroid asteroid in asteroids)
            {
                DrawModel(graphics, models[asteroid.model],
                    Matrix.CreateScale(asteroid.size) * 
                    Matrix.CreateFromAxisAngle(asteroid.rotationDirection, asteroid.rotationAngle) * 
                    Matrix.CreateTranslation(asteroid.position));
            }
        }


        private void DrawModel(GraphicsDeviceManager graphics, Model model, Matrix world)
        {
            Matrix[] transforms = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(transforms);

            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * world;
                    effect.View = game.Camera.View;
                    effect.Projection = game.Camera.Projection;
                }
                mesh.Draw();
            }
        }

        private Vector3 RandomVector(float min, float max)
        {
            return new Vector3((float)(min + (rand.NextDouble() * (max - min))),
                (float)(min + (rand.NextDouble() * (max - min))),
                (float)(min + (rand.NextDouble() * (max - min))));
        }

        public void CreateAsteroids(int number)
        {
            while (asteroids.Count < number)
            {
                Asteroid asteroid = new Asteroid();
                asteroid.model = rand.Next(4);
                asteroid.position = RandomVector(-3000, 3000);
                asteroid.velocity = RandomVector(-300, 300);
                asteroid.rotationDirection = RandomVector(0, 1);
                asteroid.size = rand.Next(4,10);
                asteroid.rotationSpeed = rand.Next(1, 5);
                if (asteroid.position.Length() < 1500)
                    continue;

                asteroids.AddLast(asteroid);
            }

        }

        /// <summary>
        /// Creates a fragment of a parent asteroid.
        /// </summary>
        /// <param name="parentPos">Parent asteroid position</param>
        /// <param name="parentVel">Parent asteroid velocity</param>
        public void CreateAsteroid(Vector3 position, Vector3 velocity, float size)
        {
            Asteroid asteroid = new Asteroid();
            asteroid.model = rand.Next(4);
            asteroid.position = position;
            asteroid.velocity = velocity;
            asteroid.size = size;
            asteroid.rotationDirection = RandomVector(0, 1);
            asteroid.rotationSpeed = rand.Next(1, 5);
            asteroids.AddLast(asteroid);                   
        }

                /// <summary>
        /// Tests a laser point to see if it is in the bounding sphere of any of 
        /// our asteroids.  If so, it deletes the asteroid and
        /// returns true.
        /// </summary>
        /// <param name="position">Tip of the laser</param>
        /// <returns></returns>
        public bool TestLaserForCollision(Vector3 position)
        {
            for (LinkedListNode<Asteroid> asteroidNode = asteroids.First; asteroidNode != null; )
            {
                LinkedListNode<Asteroid> nextNode = asteroidNode.Next;
                Asteroid asteroid = asteroidNode.Value;
                // Obtain a bounding sphere for the astroid. I can get away
                // with this here because I know the model has exactly one mesh
                // and exactly one bone.
                BoundingSphere bs = models[asteroid.model].Meshes[0].BoundingSphere;
                bs = bs.Transform(models[asteroid.model].Bones[0].Transform);

                // Move this to world coordinates. Note how east it is to
                // transform a bounding sphere
                bs.Radius *= asteroid.size;
                bs.Center += asteroid.position;

                if ((position - bs.Center).LengthSquared() < bs.Radius * bs.Radius)
                {
                    //We have a hit. We'll delete this asteroid and return true
                    FragmentAsteroid(asteroid, rand.Next(2,5));
                    game.score += 100 / asteroid.size;
                    asteroids.Remove(asteroidNode);
                    return true;
                }
                asteroidNode = nextNode;
            }



            return false;
        }

        /// <summary>
        /// Test for a collision on a sphere.
        /// </summary>
        /// <param name="sphere">Sphere being tested for collision</param>
        /// <returns></returns>
        public bool TestSphereForCollision(BoundingSphere sphere)
        {
            for (LinkedListNode<Asteroid> asteroidNode = asteroids.First; asteroidNode != null; )
            {
                LinkedListNode<Asteroid> nextNode = asteroidNode.Next;
                Asteroid asteroid = asteroidNode.Value;

                // Obtain a bounding sphere for the asteroid.  I can get away
                // with this here because I know the model has exactly one mesh
                // and exactly one bone.
                BoundingSphere bs = models[asteroid.model].Meshes[0].BoundingSphere;
                bs = bs.Transform(models[asteroid.model].Bones[0].Transform);

                // Move this to world coordinates.  Note how easy it is to 
                // transform a bounding sphere
                bs.Radius *= asteroid.size;
                bs.Center += asteroid.position;

                if (sphere.Intersects(bs))
                {
                    return true;
                }

                asteroidNode = nextNode;
            }

            return false;
        }

        public void Reset()
        {
            asteroids.Clear();
            CreateAsteroids(12);
        }

        public void FragmentAsteroid(Asteroid asteroid, int numPieces)
        {
            List<Vector3> vecList = new List<Vector3>();
            Vector3 vectorSum = Vector3.Zero;
            Vector3 tmp;
            if ((asteroid.size / numPieces * 1.5f) < 1)
                return;

            for (int i = 0; i < numPieces; i++)
            {
                tmp = RandomVector(-500, 500);
                vectorSum += tmp;
                vecList.Add(tmp);
            }
            vectorSum /= numPieces;

            for (int i = 0; i < numPieces; i++)
            {
                CreateAsteroid(asteroid.position, vecList.First() + (asteroid.velocity - vectorSum), asteroid.size / numPieces * 1.5f);
                vecList.Remove(vecList.First());
            }  
        }
    }
}
